#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <pthread.h>

#include "logger.h"
#include "ui_socket.h"

#define LEVEL_CHARS 5 /* size of level string */
#define DATE_CHARS 7 /* size of date string */
#define TIME_CHARS 9 /* size of time string */
#define MAX_LOGSTRING_LENGTH 1024 /* maximum size of the logging information */

#ifdef COMPATIBILITY_LOGGER
int init_log(const char* pLogFile, unsigned pFlags)
{
	return init_log(pFlags, pLogFile);
}
#endif

int init_log(unsigned int pFlags, const char* pLogFile)
{
	return CLogger::getInstance()->init_log(pFlags, pLogFile);
}

int printl_raw(bool pConsoleOnly, unsigned pFlags, const char* pSource, const char* pFormat, va_list pArgList)
{

	/* log format:
	 * [date [time]] level:source message */

	if (pFlags &  LL_ERR)
	{
		CLogger::getInstance()->printl_raw(pConsoleOnly, LOG_ERR, pSource, pFormat, pArgList);
	}
	else if (pFlags & LL_WARN)
	{
		CLogger::getInstance()->printl_raw(pConsoleOnly, LOG_WARN, pSource, pFormat, pArgList);
	}
	else /* default logging level is LOG_DEBUG */
	{
		CLogger::getInstance()->printl_raw(pConsoleOnly, LOG_DEBUG, pSource, pFormat, pArgList);
	}

	return 0;
}

int printl_console(unsigned int pFlags, const char* pSource, const char* pFormat, ...)
{
	va_list lArgs;
	va_start(lArgs, pFormat);
	int lReturn = printl_raw(true, pFlags, pSource, pFormat, lArgs);
	va_end(lArgs);

	return lReturn;
}

int printl(unsigned int pFlags, const char* pSource, const char* pFormat, ...)
{
	va_list lArgs;
	va_start(lArgs, pFormat);
	int lReturn = printl_raw(false, pFlags, pSource, pFormat, lArgs);
	va_end(lArgs);

	return lReturn;
}

int cleanup_log()
{
	return CLogger::destroy();
}

CLogger* CLogger::getInstance()
{
  while(pthread_mutex_lock(&sSync)) {
    /* printl(LL_ERR, __FILE__, "Fatal error: Can't lock mutex! try again.\n"); */
  }

  if(!sInstance) {
    // create the first and only object (or a destroy call was between)
    sInstance = new CLogger();
  }
  if(pthread_mutex_unlock(&sSync)) {
    /* printl(LL_ERR, __FILE__, "Fatal error: Can't unlock mutex!\n"); */
  }
  return sInstance;
}

int CLogger::destroy()
{
  while(pthread_mutex_lock(&sSync)) {
    /* printl(LL_ERR, __FILE__, "Fatal error: Can't lock mutex! try again.\n"); */
  }
  if(sInstance) {
    delete sInstance;
    sInstance = NULL;
  }
  if(pthread_mutex_unlock(&sSync)) {
    /* printl(LL_ERR, __FILE__, "Fatal error: Can't unlock mutex!\n"); */
  }
	return 0;
}

const unsigned int CLogger::LOG_DATE = 0x0001;
const unsigned int CLogger::LOG_TIME = 0x0002;
const unsigned int CLogger::LOG_FILE = 0x0004;
const unsigned int CLogger::LOG_STDERR = 0x0008;
const unsigned int CLogger::LOG_SYSLOG = 0x0010;
const unsigned int CLogger::LOG_SOCKET = 0x0020;

const unsigned int CLogger::sDefaultFlags = CLogger::LOG_DATE | CLogger::LOG_TIME | CLogger::LOG_STDERR;

int CLogger::init_log(unsigned int pFlags, const char* pLogFile)
{
	pthread_mutex_lock(&sSync);
	if(mIsInitialized)
	{
		pthread_mutex_unlock(&sSync);
		return -1;
	}

	if(mHandle.is_open())
	{
		pthread_mutex_unlock(&sSync);
		return -1; /* logger cannot be initialized twice */
	}

	if (pFlags) { /* set additional flags if given */
		mFlags |= pFlags;
	}

	if(pLogFile && (mFlags & LOG_FILE))
	{
		mHandle.open(pLogFile);
		if(!mHandle)
		{
			pthread_mutex_unlock(&sSync);
			return -1; /* file could not be opened for writing */
		}
	}

	if (mFlags & LOG_SYSLOG) { /* initialize syslog */
		; /* not yet implemented */
	}

	mIsInitialized = true;
	pthread_mutex_unlock(&sSync);
	return 0;
}

int CLogger::change_flags(unsigned int pFlags)
{
	if(pFlags)
	{
		mFlags = pFlags;
	}

	return 0;
}

int CLogger::printl_console(enum ELogLevel pLogLevel, const char* pSource, const char* pFormat, ...)
{
	va_list lArgList;

	va_start(lArgList, pFormat);
	int lReturn = printl_raw(true, pLogLevel, pSource, pFormat, lArgList);
	va_end(lArgList);

	return lReturn;
}

int CLogger::printl(enum ELogLevel pLogLevel, const char* pSource, const char* pFormat, ...)
{
	va_list lArgList;

	va_start(lArgList, pFormat);
	int lReturn = printl_raw(false, pLogLevel, pSource, pFormat, lArgList);
	va_end(lArgList);

	return lReturn;
}

int CLogger::printl_raw(bool pConsoleOnly, enum ELogLevel pLogLevel, const char* pSource, const char* pFormat, va_list pArgList)
{
	/* log format:
	 * [date [time]] level:source message */

	unsigned lFlags, lDateTime = 0/*, lMetaLength*/;
	va_list lArgs;
	char lDateStr[DATE_CHARS + 1], lTimeStr[TIME_CHARS + 1];
	char lMetaStr[MAX_LOGSTRING_LENGTH];
	char lLogStr[MAX_LOGSTRING_LENGTH];
	struct tm* lTime;
	time_t lTimestamp;

	memset(lDateStr, '\0', DATE_CHARS + 1);
	memset(lTimeStr, '\0', TIME_CHARS + 1);

	/* check optional date and time */
	if (mFlags & LOG_DATE) {
		time(&lTimestamp);
		lTime = localtime(&lTimestamp);
		strftime(lDateStr, DATE_CHARS + 1, "%b %d", lTime);
		lDateStr[DATE_CHARS] = '\0';

		lDateTime += DATE_CHARS;

		if (mFlags & LOG_TIME) {
			strftime(lTimeStr, TIME_CHARS + 1, "%H:%M:%S", lTime);
			lTimeStr[TIME_CHARS] = '\0';

			lDateTime += TIME_CHARS;
		}
	}

	/* calculate length of meta info before message and allocate a buffer */
	/* lMetaLength = lDateTime + LEVEL_CHARS + 1 + strlen(pSource) + 2; */

	snprintf(lMetaStr, MAX_LOGSTRING_LENGTH, "%s %s %s:%s ",
			lDateStr, /* optional date */
			lTimeStr, /* optional time */
			(pLogLevel == LOG_ERR)   ? "ERROR" : /* log level */
			(pLogLevel & LOG_WARN)  ? "WARN " :
			(pLogLevel & LOG_DEBUG) ? "DEBUG" :
			"*****",
			pSource /* sender of log message */
			);

	/* write log information */
	vsnprintf(lLogStr, MAX_LOGSTRING_LENGTH, pFormat, pArgList);

	if (!pConsoleOnly && (mFlags & LOG_FILE) && mHandle.is_open()) { /* write to logfile */
		pthread_mutex_lock(&sSync);
		mHandle << lMetaStr;
		mHandle << lLogStr;
		if (pFormat[strlen(pFormat) - 1] != '\n') {
			mHandle << std::endl;
		}
		pthread_mutex_unlock(&sSync);
	}

	if (mFlags & LOG_STDERR) { /* write to stderr */
		pthread_mutex_lock(&sSync);
		std::cerr << lMetaStr;
		std::cerr << lLogStr;
		if (pFormat[strlen(pFormat) - 1] != '\n') {
			std::cerr << std::endl;
		}
		pthread_mutex_unlock(&sSync);
	}

	if (!pConsoleOnly && mFlags & LOG_SOCKET)
	{
		snprintf(lMetaStr, MAX_LOGSTRING_LENGTH, "%s %s:%s ",
				lDateStr, /* optional date */
				lTimeStr, /* optional time */
				pSource /* sender of log message */
				);
		int lLogStrLen(strlen(lLogStr));
		if (lLogStr[lLogStrLen - 1] == '\n')
		{
			lLogStr[lLogStrLen - 1] = '\0';
		}
		CUISocket::getInstance()->sendInfo(pLogLevel, 
				lMetaStr, strlen(lMetaStr),
				lLogStr, lLogStrLen);

		return -1; /* error */
	}

	if (!pConsoleOnly && mFlags & LOG_SYSLOG) 
	{ 
		; /* not yet implemented */
	}

	return 0;
}

CLogger::CLogger() : 
	mIsInitialized(false),
	mFlags(sDefaultFlags)
{
}

CLogger::~CLogger()
{
	if(mHandle.is_open())
	{
		mHandle.close();
	}
}

CLogger* CLogger::sInstance = NULL;
pthread_mutex_t CLogger::sSync = PTHREAD_MUTEX_INITIALIZER;

